/****************************************************************************
**
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the demonstration applications of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia.  For licensing terms and
** conditions see http://qt.digia.com/licensing.  For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights.  These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met: http://www.gnu.org/copyleft/gpl.html.
**
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "QxItemCircleAnimation.hpp"
#include "QxDemoItemAnimation.hpp"
#include "QxColors.hpp"
#include "QxMenuManager.hpp"
#include "QxMainWindow.hpp"
#include "QxMenuManager.hpp"

static QGraphicsScene* sscene;

//////////////////// POST EFFECT STUFF ////////////////////////////////////////

class TickerPostEffect
{
public:

  virtual ~TickerPostEffect() {}
  virtual void tick( float ) {}
  virtual void transform( QxDemoItem*, QPointF& ) {}
};

class PostRotateXY : public TickerPostEffect
{
public:

  float currRotX, currRotY;
  float speedx, speedy, curvx, curvy;

  PostRotateXY( float speedx, float speedy, float curvx, float curvy )
    : currRotX( 0 ), currRotY( 0 ),
      speedx( speedx ), speedy( speedy ),
      curvx( curvx ), curvy( curvy ) {}

  void tick( float adjust ) {
    currRotX += speedx * adjust;
    currRotY += speedy * adjust;
  }

  void transform( QxDemoItem* item, QPointF& pos ) {
    QxDemoItem* parent = (QxDemoItem*) item->parentItem();
    QPointF center = parent->boundingRect().center();
    pos.setX( center.x() + (pos.x() - center.x()) * cos(currRotX + pos.x() * curvx) );
    pos.setY( center.y() + (pos.y() - center.y()) * cos(currRotY + pos.y() * curvy) );
  }
};

class PostRotateXYTwist : public TickerPostEffect
{
public:

  float currRotX, currRotY;
  float speedx, speedy, curvx, curvy;

  PostRotateXYTwist( float speedx, float speedy, float curvx, float curvy )
    : currRotX( 0 ), currRotY( 0 ),
      speedx( speedx ), speedy( speedy ),
      curvx( curvx ), curvy( curvy ) {}

  void tick( float adjust ) {
    currRotX += speedx * adjust;
    currRotY += speedy * adjust;
  }

  void transform( QxDemoItem* item, QPointF& pos ) {
    QxDemoItem* parent = (QxDemoItem*) item->parentItem();
    QPointF center = parent->boundingRect().center();
    pos.setX(center.x() + (pos.x() - center.x()) * cos(currRotX + pos.y() * curvx));
    pos.setY(center.y() + (pos.y() - center.y()) * cos(currRotY + pos.x() * curvy));
  }
};

//////////////////// TICKER EFFECT STUFF //////////////////////////////////////

class TickerEffect
{
  TickerPostEffect* postEffect;

public:

  enum EffectStatus{
    Normal,
    Intro,
    Outro
  } status;

  LetterList* letters;
  float morphSpeed, moveSpeed;
  float normalMorphSpeed, normalMoveSpeed;
  bool useSheepDog, morphBetweenModels;

  TickerEffect( LetterList* letters )
    : postEffect( new TickerPostEffect() ), status( Intro ), letters( letters ),
      morphSpeed( QxColors::tickerMorphSpeed ), moveSpeed( QxColors::tickerMoveSpeed ),
      normalMorphSpeed( QxColors::tickerMorphSpeed ), normalMoveSpeed( QxColors::tickerMoveSpeed ),
      useSheepDog( true ), morphBetweenModels( !QxColors::noTickerMorph ) {}

  void setPostEffect( TickerPostEffect* effect ) {
    delete postEffect;
    postEffect = effect;
  }

  virtual ~TickerEffect() {
    delete postEffect;
  }

  void slowDownAfterIntro( float adjust ) {
    if( morphBetweenModels ) {
      if( status == Intro ) {
        float dec = 0.1 * adjust;
        moveSpeed -= dec;
        if( moveSpeed < QxColors::tickerMoveSpeed ) {
          moveSpeed = normalMoveSpeed;
          morphSpeed = normalMorphSpeed;
          status = Normal;
        }
      }
    }
  }

  void moveLetters( float adjust ) {
    float adaptedMoveSpeed = this->moveSpeed * adjust;
    float adaptedMorphSpeed = this->morphSpeed * adjust;
    postEffect->tick(adjust);

    for( int i=0; i<letters->size(); ++i ) {
      QxLetterItem* letter = letters->at( i );
      letter->guideAdvance( this->morphBetweenModels ? adaptedMoveSpeed : QxColors::tickerMoveSpeed );
      letter->guideMove( this->morphBetweenModels ? adaptedMorphSpeed : -1 );

      QPointF pos = letter->getGuidedPos();
      postEffect->transform( letter, pos );

      if( useSheepDog ) {
        letter->setPosUsingSheepDog( pos, QRectF(0, 0, 800, 600) );
      } else {
        letter->setPos( pos );
      }
    }
  }

  virtual void tick( float adjust ) {
    slowDownAfterIntro( adjust );
    moveLetters( adjust );
  }
};

class EffectWhirlWind : public TickerEffect
{
public:

  EffectWhirlWind( LetterList* letters) : TickerEffect( letters ) {
    moveSpeed = 50;
    for( int i = 0; i < this->letters->size(); ++i ) {
      QxLetterItem* letter = this->letters->at( i );
      letter->setGuidedPos( QPointF(0, 100) );
    }
  }
};

class EffectSnake : public TickerEffect
{
public:

  EffectSnake( LetterList* letters) : TickerEffect( letters ) {
    moveSpeed = 40;
    for( int i = 0; i < this->letters->size(); ++i ) {
      QxLetterItem* letter = this->letters->at( i );
      letter->setGuidedPos( QPointF(0, -250 - (i * 5)) );
    }
  }
};

class EffectScan : public TickerEffect
{
public:

  EffectScan( LetterList* letters ) : TickerEffect( letters ) {
    for( int i = 0; i < this->letters->size(); ++i ) {
      QxLetterItem* letter = this->letters->at( i );
      letter->setGuidedPos( QPointF(100, -300) );
    }
  }
};

class EffectRaindrops : public TickerEffect
{
public:

  EffectRaindrops( LetterList* letters) : TickerEffect( letters ) {
    for( int i = 0; i < this->letters->size(); ++i ) {
      QxLetterItem* letter = this->letters->at( i );
      letter->setGuidedPos( QPointF(-100 + rand() % 200, - 200.0f - rand() % 1300) );
    }
  }
};

class EffectLine : public TickerEffect
{
public:

  EffectLine( LetterList* letters ) : TickerEffect( letters ) {
    for( int i = 0; i < this->letters->size(); ++i ) {
      QxLetterItem* letter = this->letters->at( i );
      letter->setGuidedPos( QPointF(100, 500.0f + i * 20) );
    }
  }
};

//////////////////// TICKER STUFF /////////////////////////////////////////////

QxItemCircleAnimation::QxItemCircleAnimation( QGraphicsScene* scene, QGraphicsItem* parent )
  : QxDemoItem( scene, parent )
{
  sscene                   = scene;
  this->letterCount        = QxColors::tickerLetterCount;
  this->scale              = 1;
  this->showCount          = -1;
  this->tickOnPaint        = false;
  this->paused             = false;
  this->doIntroTransitions = true;
  this->setAcceptsHoverEvents(true);
  this->setCursor(Qt::OpenHandCursor);
  this->setupGuides();
  this->setupLetters();
  this->useGuideQt();
  this->effect = 0;//new TickerEffect(this->letterList);
}

QxItemCircleAnimation::~QxItemCircleAnimation()
{
  delete this->letterList;
  delete this->qtGuide1;
  delete this->qtGuide2;
  delete this->qtGuide3;
  delete this->effect;
}

void QxItemCircleAnimation::createLetter( char c )
{
  QxLetterItem* letter = new QxLetterItem( c, sscene, this );
  this->letterList->append( letter );
}

void QxItemCircleAnimation::setupLetters()
{
  this->letterList = new LetterList();

  QString s = QxColors::tickerText;
  int len = s.length();
  int i = 0;
  for( ; i < this->letterCount - len; i += len ) {
    for( int l = 0; l < len; ++l ) {
      createLetter( s[l].toLatin1() );
    }
  }

  // Fill inn with blanks:
  for( ; i < this->letterCount; ++i ) {
    createLetter( ' ' );
  }
}

void QxItemCircleAnimation::setupGuides()
{
  int x = 0;
  int y = 20;

  this->qtGuide1 = new QxGuideCircle( QRectF(x, y, 260, 260), -36, 342 );
  this->currGuide = 0;
  new QxGuideLine( QPointF(x + 240, y + 268), this->qtGuide1 );
  new QxGuideLine( QPointF(x + 265, y + 246), this->qtGuide1 );
  new QxGuideLine( QPointF(x + 158, y + 134), this->qtGuide1 );
  new QxGuideLine( QPointF(x + 184, y + 109), this->qtGuide1 );
  new QxGuideLine( QPointF(x + 160, y +  82), this->qtGuide1 );
  new QxGuideLine( QPointF(x +  77, y + 163), this->qtGuide1 ); // T-top
  new QxGuideLine( QPointF(x + 100, y + 190), this->qtGuide1 );
  new QxGuideLine( QPointF(x + 132, y + 159), this->qtGuide1 );
  new QxGuideLine( QPointF(x + 188, y + 211), this->qtGuide1 );
  new QxGuideCircle( QRectF(x + 30, y + 30, 200, 200), -30, 336, QxGuideCircle::CW, this->qtGuide1 );
  new QxGuideLine( QPointF(x + 238, y + 201), this->qtGuide1);

  y = 30;
  this->qtGuide2 = new QxGuideCircle( QRectF(x + 30, y + 30, 200, 200), 135, 270, QxGuideCircle::CCW );
  new QxGuideLine( QPointF(x + 222, y + 38), this->qtGuide2 );
  new QxGuideCircle( QRectF(x, y, 260, 260), 135, 270, QxGuideCircle::CW, this->qtGuide2 );
  new QxGuideLine( QPointF(x + 59, y + 59), this->qtGuide2 );

  x = 115;
  y = 10;
  this->qtGuide3 = new QxGuideLine( QLineF(x, y, x + 30, y) );
  new QxGuideLine( QPointF(x + 30, y + 170), this->qtGuide3 );
  new QxGuideLine( QPointF(x, y + 170), this->qtGuide3 );
  new QxGuideLine( QPointF(x, y), this->qtGuide3 );

  this->qtGuide1->setFence( QRectF(0, 0, 800, 600) );
  this->qtGuide2->setFence( QRectF(0, 0, 800, 600) );
  this->qtGuide3->setFence( QRectF(0, 0, 800, 600) );
}

void QxItemCircleAnimation::useGuide( QxGuide* guide, int firstLetter, int lastLetter )
{
  float padding = guide->lengthAll() / float(lastLetter - firstLetter);
  for( int i = firstLetter; i < lastLetter; ++i ) {
    QxLetterItem* letter = this->letterList->at( i );
    letter->useGuide( guide, (i - firstLetter) * padding );
  }
}

void QxItemCircleAnimation::useGuideQt()
{
  if( this->currGuide != this->qtGuide1 ) {
    this->useGuide( qtGuide1, 0, this->letterCount );
    this->currGuide = qtGuide1;
  }
}

void QxItemCircleAnimation::useGuideTt()
{
  if( this->currGuide != this->qtGuide2 ) {
    int split = int(this->letterCount * 5.0 / 7.0);
    this->useGuide( qtGuide2, 0, split );
    this->useGuide( qtGuide3, split, this->letterCount );
    this->currGuide = qtGuide2;
  }
}

QRectF QxItemCircleAnimation::boundingRect() const
{
  return QRectF(0, 0, 300, 320);
}

void QxItemCircleAnimation::prepare()
{
}

void QxItemCircleAnimation::switchToNextEffect()
{
  ++this->showCount;
  delete this->effect;

  switch( this->showCount ) {
  case 1: this->effect = new EffectSnake( this->letterList ); break;

  case 2: {
    this->effect = new EffectLine( this->letterList );
    this->effect->setPostEffect( new PostRotateXYTwist( 0.01f, 0.0f, 0.003f, 0.0f ) );
  } break;

  case 3: {
    this->effect = new EffectRaindrops( this->letterList );
    this->effect->setPostEffect( new PostRotateXYTwist( 0.01f, 0.005f, 0.003f, 0.003f ) );
  } break;

  case 4: {
    this->effect = new EffectScan(this->letterList);
    this->effect->normalMoveSpeed = 0;
    this->effect->setPostEffect( new PostRotateXY( 0.008f, 0.0f, 0.005f, 0.0f ) );
  } break;

  default: {
    this->showCount = 0;
    this->effect = new EffectWhirlWind( this->letterList );
  }
  }
}

void QxItemCircleAnimation::animationStarted( int id )
{
  if( id == QxDemoItemAnimation::ANIM_IN ) {
    if( this->doIntroTransitions ) {
      // Make all letters dissapear
      for( int i = 0; i < this->letterList->size(); ++i ) {
        QxLetterItem* letter = this->letterList->at( i );
        letter->setPos( 1000, 0 );
      }
      this->switchToNextEffect();
      this->useGuideQt();
      this->scale = 1;
      // The first time we run, we have a rather large
      // delay to perform benchmark before the ticker shows.
      // But now, since we are showing, use a more appropriate value:
      this->currentAnimation->startDelay = 1500;
    }
  }  else if( this->effect ) {
    this->effect->useSheepDog = false;
  }

  this->tickTimer = QTime::currentTime();
}

void QxItemCircleAnimation::animationStopped(int)
{
  // Nothing to do.
}

void QxItemCircleAnimation::swapModel(){
  if( this->currGuide == this->qtGuide2 ) {
    this->useGuideQt();
  } else {
    this->useGuideTt();
  }
}

void QxItemCircleAnimation::hoverEnterEvent( QGraphicsSceneHoverEvent* )
{
  //  Skip swap here to enhance ticker dragging
  //    this->swapModel();
}

void QxItemCircleAnimation::hoverLeaveEvent( QGraphicsSceneHoverEvent* )
{
  this->swapModel();
}

void QxItemCircleAnimation::setTickerScale( float s )
{
  this->scale = s;
  qtGuide1->setScale( this->scale, this->scale );
  qtGuide2->setScale( this->scale, this->scale );
  qtGuide3->setScale( this->scale, this->scale );
}

void QxItemCircleAnimation::mousePressEvent( QGraphicsSceneMouseEvent* event )
{
  this->mouseMoveLastPosition = event->scenePos();
  if( event->button() == Qt::LeftButton ) {
    this->setCursor( Qt::ClosedHandCursor );
  } else {
    this->switchToNextEffect();
  }
}

void QxItemCircleAnimation::mouseReleaseEvent( QGraphicsSceneMouseEvent* event )
{
  if( event->button() == Qt::LeftButton ) {
    this->setCursor( Qt::OpenHandCursor );
  }
}

void QxItemCircleAnimation::mouseMoveEvent( QGraphicsSceneMouseEvent* event )
{
  QPointF newPosition = event->scenePos();
  this->setPosUsingSheepDog( this->pos() + newPosition - this->mouseMoveLastPosition, QRectF(-260, -280, 1350, 1160) );
  this->mouseMoveLastPosition = newPosition;
}

void QxItemCircleAnimation::wheelEvent( QGraphicsSceneWheelEvent* event )
{
  this->effect->moveSpeed = this->effect->moveSpeed + (event->delta() > 0 ? -0.20 : 0.20);
  if( this->effect->moveSpeed < 0 ) {
    this->effect->moveSpeed = 0;
  }
}

void QxItemCircleAnimation::pause( bool on )
{
  this->paused = on;
  this->tickTimer = QTime::currentTime();
}

void QxItemCircleAnimation::tick()
{
  if( this->paused || !this->effect ) {
    return;
  }

  float t = this->tickTimer.msecsTo( QTime::currentTime() );
  this->tickTimer = QTime::currentTime();
  this->effect->tick( t/10.0f );
}

void QxItemCircleAnimation::paint( QPainter*, const QStyleOptionGraphicsItem*, QWidget* )
{
  if( this->tickOnPaint ) {
    tick();
  }
}
